﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace CVML
{
    public class Parser
    {
        public static List<Term> ParseStream(TextReader tr)
        {
            var p = new Parser(tr);
            var r = new List<Term>();
            while (!p.Eof)
            {
                var t = p.ParseTerm();
                if (t != null)
                    r.Add(t);
            }
            r.Add(new Op(CVM.OpCode.Op_ret));            
            return r;
        }

        public static List<Term> ParseString(string s)
        {
            return ParseStream(new StringReader(s));
        }

        #region fields
        TextReader reader;
        string s;
        int n;
        Dictionary<string, Term> defs = new Dictionary<string, Term>();
        #endregion 

        public Parser(TextReader reader)
        {
            this.reader = reader;
            s = reader.ReadToEnd();
            EatWSpace();
        }

        public char Current
        {
            get { return s[n]; }
        }

        public bool Eof
        {
            get { return n >= s.Length; }
        }

        public void Advance()
        {
            ++n;
            if (n >= s.Length)
                return;
        }

        public void EatWSpace()
        {
            while (!Eof && Char.IsWhiteSpace(Current))
                Advance();
        }

        public void Eat(Char c)
        {
            if (Current != c)
                throw new Exception("Expected character: " + c);
            Advance();
        }

        public bool IsDef(string s)
        {
            return defs.ContainsKey(s);
        }

        public static bool IsDelim(char c)
        {
            return "(){}[]:\t\n ".Contains(c);
        }

        public Term ParseTerm()
        {
            Term r;
            if (Current == '(')
                r = ParseList();
            else if (Current == '{')
                r = ParseTable();
            else if (Current == '[')
                r = ParseFunction();
            else if (Char.IsNumber(Current))
                r = ParseNumber();
            else
                r = ParseName();
            EatWSpace();
            return r;
        }

        public Constant ParseConstant()
        {
            var r = ParseTerm() as Constant;
            if (r == null) throw new Exception("Cosntant was expected");
            return r;
        }

        public KTable ParseTable()
        {
            return ParseTable(new KTable());
        }

        public KTable ParseTable(KTable table)
        {
            Eat('{');
            while (Current != '}')
            {
                var k = ParseConstant();
                if (!(k is KInt))
                    throw new Exception("Currently only integer keys are supported");
                table.Keys.Add(k);
                Eat(':');
                EatWSpace();
                table.Values.Add(ParseConstant());
            }
            Eat('}');
            return table;
        }

        public KList ParseList()
        {
            return ParseList(new KList());
        }

        public KList ParseList(KList list)
        {
            Eat('(');
            while (Current != ')')
                list.Data.Add(ParseConstant());
            Eat(')');
            return list;
        }

        public KFxn ParseFunction()
        {
            return ParseFunction(new KFxn());
        }

        public KFxn ParseFunction(KFxn f)
        {
            Eat('[');
            while (Current != ']')
            {
                var term = ParseTerm();
                if (term != null)
                    f.Terms.Add(term);
            }
            f.Terms.Add(new Op(CVM.OpCode.Op_ret));
            Eat(']');
            return f;
        }

        public string ParseToken()
        {
            var sb = new StringBuilder();
            while (!Eof && !IsDelim(Current))
            {
                sb.Append(Current);
                Advance();
            }
            return sb.ToString();
        }

        public Term ParseNumber()
        {
            var s = ParseToken();
            int n;
            if (Int32.TryParse(s, out n))
                return new KInt(n);
            float f;
            if (float.TryParse(s, out f))
                return new KFloat(f);
            throw new Exception("Unrecognized number " + s);
        }

        public void ParseDef(string name)
        {
            EatWSpace();
            switch (Current)
            {
                case '(':
                    var list = new KList();
                    defs.Add(name, list);
                    ParseList(list);
                    break;
                case '[':
                    var fxn = new KFxn();
                    defs.Add(name, fxn);
                    ParseFunction(fxn);
                    break;
                case '{':
                    var tbl = new KTable();
                    defs.Add(name, tbl);
                    ParseTable(tbl);
                    break;
                default:
                    var a = ParseTerm();
                    if (a == null)
                        throw new Exception("A definition cannot be defined as a definition");
                    defs.Add(name, a);
                    break;
            }
        }

        public Term ParseName()
        {
            Debug.Assert(Char.IsLetter(Current) || Current == '_');
            
            string s = ParseToken();
            if (s[s.Length - 1] == '=')
            {
                ParseDef(s.Substring(0, s.Length - 1));
                return null;
            }

            if (Op.IsOpCode(s))
                return new Op(s);

            if (IsDef(s))
                return defs[s];

            throw new Exception("Not a recognized name: " + s);
        }
    }
}
